# -*- coding: utf-8 -*-
import os
import sys
import wsgiref.handlers
from accost import *
from werkzeug import script, Client, Response
from werkzeug.contrib import profiler

class nul:
    write = staticmethod(lambda s: None)


def runcgi(application, before_daemon=None):
    def action( hostname            =   ('h', ''),
                port                =   ('p', 3001),
                protocol            =   'scgi',
                method              =   'threaded',
                children            =   20,
                daemonize           =   False,
                pidfile             =   '/var/run/accost.pid',
                workdir             =   '.',
                outlog              =   '/dev/null',
                errlog              =   '/dev/null',
                umask               =   022,
        ):
        """run application use flup
        you can choose these arguments:
        protocol :   scgi, fcgi or ajp
        method   :   threaded or fork
        children :   number of threads or processes"""
        if protocol not in ('scgi', 'fcgi', 'ajp'):
            sys.exit('invalid protocol: %s'%protocol)
        flup_module = 'server.%s'%protocol

        kw = {'maxSpare': children, 'minSpare': children}

        if protocol == 'scgi':
            kw['scriptName'] = '/'

        if method == 'threaded':
            kw['maxThreads'] = children
        elif method == 'fork':
            flup_module += '_fork'
            kw['maxChildren'] = children
        else:
            sys.exit('invalid method: %s'%method)

        try:
            WSGIServer = getattr(__import__('flup.' + flup_module, '', '', flup_module), 'WSGIServer')
        except ImportError:
            print "You need to install flup"
            sys.exit()

        if os.name!='posix' and outlog=='/dev/null':
            outlog = None
        if os.name!='posix' and errlog=='/dev/null':
            errlog = None

        if daemonize:
            from accost.utils.daemonize import become_daemon
            become_daemon(workdir, outlog, errlog, umask)

        if daemonize and pidfile and os.name=='posix':
            try:
                fp = open(pidfile, 'w')
                fp.write("%d\n" % os.getpid())
                fp.close()
            except:
                pass

        if before_daemon is not None:
            before_daemon()

        WSGIServer(application, bindAddress=(hostname, port), **kw).run()

    return action


def run_cherrypy_server(application):
    def action( hostname            =   ('h', ''),
                port                =   ('p', 8000),
                threads             =   10,
                server_name         =   '0.0.0.0',
                max                 =   -1,
                request_queue_size  =   5,
                timeout             =   10,
                daemonize           =   False,
                pidfile             =   '/var/run/accost-cherrypy.pid',
                workdir             =   '.',
                outlog              =   '/dev/null',
                errlog              =   '/dev/null',
                umask               =   022,
        ):
        """run application use cherrypy http server"""
        if os.name!='posix' and outlog=='/dev/null':
            outlog = None
        if os.name!='posix' and errlog=='/dev/null':
            errlog = None

        if daemonize:
            from accost.utils.daemonize import become_daemon
            become_daemon(workdir, outlog, errlog, umask)

        if daemonize and pidfile and os.name=='posix':
            try:
                fp = open(pidfile, 'w')
                fp.write("%d\n" % os.getpid())
                fp.close()
            except:
                pass


        from accost.wsgiserver import CherryPyWSGIServer
        server = CherryPyWSGIServer((hostname, port), application,
                threads, server_name, max, request_queue_size, timeout)
        try:
            server.start()
        except KeyboardInterrupt:
            server.stop()

    return action


def run_asynwsgi_server(application):
    def action(hostname=('h', ''), port=('p', 8000), server_name='0.0.0.0'):
        """run application use asynwsgi http server"""
        from accost.asynwsgi import WSGIServer
        server = WSGIServer((hostname, port), application, server_name)
        server.start()

    return action


def run_tornado_server(application):
    def action(hostname=('h', ''), port=('p', 8000), server_name='0.0.0.0'):
        """run application use tornado http server"""
        from tornado.wsgi import WSGIContainer
        from tornado.httpserver import HTTPServer
        from tornado.ioloop import IOLoop
        container = WSGIContainer(application)
        server = HTTPServer(container)
        server.listen(port)
        try:
            IOLoop.instance().start()
        except KeyboardInterrupt:
            IOLoop.instance().stop()

    return action


def show_tables(app):
    def action():
        """show tables of all application databases"""
        for dbname, store in app.db.items():
            print '[%s]'%dbname
            store.showTables()

    return action


def show_create(app):
    def action():
        """show create table sql of all application databases"""
        for dbname, store in app.db.items():
            print '[%s]'%dbname
            store.showCreate()

    return action


def dbshell(app):
    def action():
        """run mysql client use database configuration of the application"""
        if len(app.db) == 0:
            sys.exit('database not found')
        elif len(app.db) == 1:
            app.db.popitem()[1].runDBShell()
        else:
            print 'There are %d databases, please choose one of them:'%len(app.db.items())
            name = input(', '.join([dbname for dbname in app.db]))
            if not name in app.db:
                sys.exit('no such database')
            app.db[name].runDBShell()

    return action


def http_get(app):
    def action(url="/"):
        """make a request use GET method"""
        c = Client(app, Response)
        resp = c.get(url)
        print resp.data

    return action


def run_eurasia_server(app):
    def action(hostname=('h', '0.0.0.0'), port=('p', 8000)):
        """run application use eurasia http server"""
        try:
            from eurasia import WSGIServer
        except ImportError:
            print "You need to install eurasia"
            sys.exit()
        server = WSGIServer(app, bindAddress=(hostname, port))
        server.run()

    return action


def run_eventlet_server(app):
    def action(hostname=('h', '0.0.0.0'), port=('p', 8000), debug=False):
        """run application use eventlet http server"""
        try:
            from eventlet import api, wsgi
        except ImportError:
            print "You need to install eventlet"
            sys.exit()
        if debug:
            log = None
        else:
            log = nul
        wsgi.server(api.tcp_listener((hostname, port)), app, log=log)

    return action


def run_twisted_server(app):
    def action(port=('p', 8000), reactor_type=('r', 0)):
        """run application use twisted http server
        @reactor_type: [default 0]
            1       epoll   reactor
            2       poll    reactor
            3       kqueue  reactor
            4       iocp    reactor
            other   select  reactor
        """
        try:
            if reactor_type == 1:
                from twisted.internet import epollreactor
                epollreactor.install()
            elif reactor_type == 2:
                from twisted.internet import pollreactor
                pollreactor.install()
            elif reactor_type == 3:
                from twisted.internet import kqreactor
                kqreactor.install()
            elif reactor_type == 4:
                from twisted.internet import iocpreactor
                iocpreactor.install()
            from twisted.internet import reactor
            from twisted.application import service, strports
            from twisted.web2 import server, channel, wsgi
        except ImportError:
            print "You need to install twisted and twisted.web2"
            sys.exit()

        application = service.Application('web2-wsgi') # call this anything you like
        site = server.Site(wsgi.WSGIResource(app))
        s = strports.service('tcp:%d'%port, channel.HTTPFactory(site))
        s.setServiceParent(application)
        s.startService()
        reactor.run()

    return action


def clean(dir=('d', '.'), postfix=('x', 'pyc'), remove=False):
    """remove the files with specify postfix"""
    check = lambda s: s.endswith(".%s"%postfix)
    for dirpath, dirnames, filenames in os.walk(dir):
        for filename in filter(check, filenames):
            p = os.path.abspath(os.path.join(dirpath, filename))
            if remove:
                print "remove %s"%p
                os.remove(p)
            else:
                print p


def show_urls(app):
    def action():
        """display url structure"""
        for rule in app.url_mapping.iter_rules():
            print "%-30s"%rule, rule.endpoint

    return action


def winsvc(app):
    def action():
        """handler windows service"""
        import win32serviceutil
        import win32service
        import win32event
        from accost.wsgiserver import CherryPyWSGIServer

        class Service(win32serviceutil.ServiceFramework):
            _svc_name_ = "accost_service"
            _svc_display_name_ = "Accost Service"
            def __init__(self, args):
                win32serviceutil.ServiceFramework.__init__(self, args)
                self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

            def SvcDoRun(self):
                self.server = CherryPyWSGIServer(('0.0.0.0', 80), app, 10)
                self.server.start()
                # 等待服务被停止
                win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)

            def SvcStop(self):
                # 先告诉SCM停止这个过程
                self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
                # 停止服务
                self.server.stop()
                # 设置事件
                win32event.SetEvent(self.hWaitStop)

        win32serviceutil.HandleCommandLine(Service, argv=sys.argv[1:])

    return action


def bshell(app):
    def action():
        from bpython import embed
        embed({"app": app})

    return action


class Command(object):
    def __init__(self, application):
        self.application = application
        self._actions = {
                'runcgi'    :   runcgi(application),
                'shell'     :   script.make_shell(lambda: {"app": application},
                                    "Interactive Accost Shell"),
                'runserver' :   script.make_runserver(lambda: application,
                                    use_reloader=True, threaded=True, hostname='0.0.0.0',
                                    port=7777, use_debugger=True),
                'cherrypy'  :   run_cherrypy_server(application),
                'asynwsgi'  :   run_asynwsgi_server(application),
                'eurasia'   :   run_eurasia_server(application),
                'eventlet'  :   run_eventlet_server(application),
                'twisted'   :   run_twisted_server(application),
                'tornado'   :   run_tornado_server(application),
                'profile'   :   profiler.make_action(lambda: application, port=7777),
                'http_get'  :   http_get(application),
                'clean'     :   clean,
                'winsvc'    :   winsvc(application),
                'bshell'    :   bshell(application),
        }

    def run(self):
        script.run(self._actions, '')

    def register(self, func, name=None):
        name = name if name is not None else func.func_name
        self._actions[name] = func

    def register_app(self, app):
        self._actions['show_create']    = show_create(app)
        self._actions['show_tables']    = show_tables(app)
        self._actions['dbshell']        = dbshell(app)
        self._actions['show_urls']      = show_urls(app)
        for name, func in getattr(app.app, "scripts", {}).items():
            try:
                if isinstance(func, basestring):
                    dot = func.rindex(".")
                    module, f = func[:dot], func[dot+1:]
                    module = __import__(module, {}, {}, [f])
                    func = getattr(module, f)
                self._actions[name] = func(app)
            except:
                pass
